How the screen displays work on the ZX81

A tutorial by Wilf Rigter
as of September 7, 1996


INDEX

  1. Introduction
  2. Basics
  3. The SLOW-MODE operating mode
  4. The FAST-MODE operating mode
  5. ZX81 Video Hardware
  6. ZX81 characters video hardware
  7. Pseudo Hires Hardware
  8. Genuine Hires Hardware
  9. Temporal sequences of character representation on the ZX81
  10. Timings at Pseudo Hires
  11. Timings for real hires
  12. Machine code routines in SLOW MODE
  13. Machine code routines in FAST MODE
  14. Machine code routines at real hires
  15. Machine code routines at Pseudo Hires

1. Introduction

Once innovative technical ideas were combined with economical design and market opportunity, some interesting things began to happen. In 1980, "Sinclair" was not yet a household name and was perhaps better known for his digital watch and calculator than his ZX80. But Sinclair decided that the time had come for an affordable, easy-to-use, mass-produced version of the ZX80 that would also feature floating-point arithmetic and a flicker-free display.

The ZX81 was born and, as they say, "the rest is history."

The video system played a key role in the inexpensive design of the ZX81. Not only was it inexpensive to produce, but it turned out to offer possibilities beyond the developers' original goals.


2. Basics of ZX81 screen display

The standard screen image consists of 24 lines of 32 characters each. Each character has a height of 8 scan lines and a width of 8 pixels. The characters to be displayed are located in a memory area called DFILE (display file). The character set of 128 displayable characters consists of 64 normal characters (white on black), which include (exclusively!) capital letters, numbers, special characters and graphic characters, and their inverted counterparts (black on white). The ZX81 character codes CHR$ 0-63, CHR$ 118 and CHR$ 128-191 do not conform to the ASCII standard. A number of screen codes are also used for keywords, functions and commands, but these are always composed of individual displayable characters before writing to DFILE.

DFILE begins with the Sinclair equivalent of a line return CR (CHR$ 118), followed by up to 32 CHR$ codes; this repeats 24 times and ends with a CHR$ 118. CHR$ 118 is the op code for the Z80 CPU's HALT instruction for reasons that will be explained later.

All other character codes are illegal and generally cause a system crash when loaded into DFILE. The small DFILE is used on ZX81 systems equipped with only 1 or 2 KB of RAM to minimize screen memory footprint. With an empty screen, DFILE only consists of 25 CHR$ 118 codes. Each line expands as characters are written to that line. When equipped with 4 KB of RAM or more, DFILE is initialized with the fully expanded format of 24 lines of 32 characters each with CHR$ 00 (space) and 25 CHR$ 118 line terminators.

The screen characters are not displayed directly, but rather serve as address pointers to a character generator table in ROM. This table is addressed using a combination of the code stored in DFILE and the ZX81 hardware. The byte it contains is loaded into the video shift register. Bit 7 of the character code is used by the video hardware to invert the pixels, if necessary, as they are shifted out of the shift register. The display on the screen is generated by a serial data stream of pixels from the video shift register, which switches the electron beam of the television's picture tube on and off while it scans the fluorescent layer inside the picture tube.

A fully expanded DFILE with 24 lines of 32 characters per line and 8 bytes of character generator data per character consists of 6144 bytes or 49152 pixels per screen.


3. The SLOW-MODE operating mode

In SLOW-MODE operating mode, the CPU constantly switches between the image generation program and the user program. Around 80% of the CPU time is used for image generation and keyboard query and only around 20% of the CPU time is available for executing the user program.

In fact, the CPU time per television field is divided into four task blocks, as shown in Table 1. An NMI generator switches between the individual tasks (NMI=non maskable interrupt). The NMI handling routine executed thereby controls the task switching between the asynchronous application program and the real-time image generation routines.

1. VSYNC, counting fields and keyboard queryNMI off
2. Empty lines / user programNMI an
3. VIDEO DISPLAY RoutineNMI off
4. Empty lines / user programNMI an

Table 1: SLOW-MODE CPU task table

Each task can be described in detail as follows:

  1. During the interlace jump (VSYNC active) during which no data is displayed, the CPU executes a VSYNC routine that requires a defined time to execute. This involves incrementing the field counter and reading eight lines of the keyboard matrix along with the 50/60Hz mode bit. The ULA keyboard port is addressed on every I/O read access where A0 is low (e.g. $FE). This also resets the ULA's 3-bit line counter (LCNCTR). After all keyboard data has been processed (400 µs later), the CPU executes an OUT FF, A (any OUT command would do the same thing) which resets the ULA's video output back to normal (white background with horizontal SYNC pulses) and prevents the LCNTR reset.

    At the end of the VSYNC routine, the number of empty lines up to the beginning of the output area of ​​the next field is determined using the system variable MARGIN (50/60Hz). The NMI generator is then switched on and the CPU register bank is switched to the register set of the application program.

  2. When the CPU executes the application program, it is interrupted every 64 µs by the NMI generator, at the same time the ULA generates an HSYNC pulse. The NMI routine increments an "empty line counter" in A' and returns to the application program if the allotted time is still sufficient. When the empty row counter became zero after incrementing, the NMI routine turns off the NMI generator and switches to the video display routine via a pointer in the IX register pair.

  3. The video display routine sets the pointer to DFILE, initializes the row and column counter, enables interrupts (EI) and jumps to the beginning of DFILE + 32k using JP (HL). Except for the N/L character that marks the end of the line, every character in DFILE is interpreted as NOP.

    At the end of the line, the line and column counters are updated and the remaining lines are executed after exiting the interrupt handler. After 192 lines, the video display routine ends by turning on the NMI generator and switching the CPU back to executing the application program.

  4. As before, the NMI routine counts the empty lines in the area of ​​the upper invisible image. At the end of the lower empty lines, the whole process is repeated by switching the NMI handling routine back to the VSYNC routine.


4. The FAST-MODE operating mode

In ZX80-compatible FAST MODE mode, the CPU runs either the video routine or some other program, but never both, causing the familiar display flickering that occurs when the CPU switches between these tasks. When the application program is running, it works at 100% of CPU time. Only when the application program is STOPped (in command entry mode), while waiting for keyboard input during the INPUT command, or when the PAUSE command is being processed, is the screen displayed.

The video hardware is activated as in SLOW MODE, except that NMI is always deactivated. In addition, the empty lines at the top and bottom of the screen are also generated by software, making the ZX81 ROM 100% compatible with the ZX80 hardware.


5. ZX81 Video Hardware

The ZX81's video hardware consists of the Z80 CPU, ROM, RAM and most of the ZX81 Sinclair Logic Chip, commonly called "ULA". An overview of all relevant connections including the buffering resistors R is shown in Figure 2.

For reasons of simplicity, only the 2KB version is shown. The ULA contains a 6.5 MHz crystal oscillator and frequency divider that produces HSYNC pulses on the video output and NMI pulses on the NMI output. The HSYNC and NMI outputs can be controlled with the following I/O commands:

1. OUT FD,A switches off the NMI generator
2. OUT FE,A switches on the NMI generator
3. IN A,FE switches off the HSYNC generator (only if NMI is off)
4. OUT FF,A switches on the HSYNC generator

 

The voltage at the ULA's video output alternates between three states. Normally it is at +5V for white, empty lines. Character patterns are displayed as black pixels with a voltage of +2.5V. For the horizontal HSYNC pulses and the wide VSYNC pulses, the voltage is 0V, as shown in Figure 1. These voltages are reduced to +1V, +0.5V and 0V (UK/US) using a voltage divider and fed to the input of the UHF modulator, which uses the video signal to generate an antenna signal that can be evaluated by the television.

 white _  _____    _____    _________//__________//____             ____//_____
 black _   |   |__|     |__|   |             |         |<--400µs-->|          |
 sync  _   |<-------64µs------>|<---64µs---->|         |_____//____|          |
               display line         blank        blank   vert sync     blank

Figure 1: Video voltages

The HSYNC pulses are 5 µs long with 64 µs spacing between them. The VSYNC pulses are 400 µs long with 16.6 ms or 20 ms spacing between them. VSYNC is used to synchronize the vertical oscillator of the television and start the picture structure at the top of the screen. This happens when IN A, FE (for querying the keyboard) pushes the video output to the SYNC level. 400   µs later the SYNC level is exited to enable the generation of HSYNC pulses every 64 µs. From now on, the HSYNC pulses will be generated again independently of the CPU until the next VSYNC.


6. ZX81 Character Video Hardware

The ZX81 character representation hardware consists of the Z80 CPU, ROM, RAM and most of the ZX81 Sinclair Logic Chip, commonly called "ULA". The block diagram is shown in Figure 2 with all relevant connections including the buffer resistors R. For reasons of simplicity, only the 2 KB version is shown.

              ULA               ROM        Z80        2K RAM
        ________________       _____       _____       _____ 
VIDEO<-| VSHFTREG <-DATA|-----|DATA |-----|DATA |--R--|DATA |  
       |  LINECTR ->A0-2|-----|A0-2 |--R--|A0-2 |-----|A0-2 |
       | CHRLATCH ->A3-8|-----|A3-8 |--R--|A3-8 |-----|A3-8 |
       |                |     |A9-12|-----|A9-13|-----|A9-11|
       |           ROMCS|-----|CE   |     |  INT|-----|A6   |
       |                |     |_____|     |     |     |     |
       |           RAMCS|-----------------|-----|-----|CE/OE|
       |             A14|-----------------|A14  |     |_____|
       |             A15|-----------------|A15  |        
       |              WR|-----------------|WR   | 
       |              RD|-----------------|RD   |
       |              M1|-----------------|M1   |
       |            MREQ|-----------------|MREQ |
       |            IORQ|-----------------|IORQ | 
       |             NMI|-----------------|NMI  |   
       |            HALT|-----------------|HALT |
       |________________|                 |_____|

Figure 2: Circuit for character representation


7. Pseudo Hires Video Hardware

The ZX81 hardware for producing the pseudo high-resolution graphics consists of the Z80 CPU, ROM, RAM and most of the ZX81 Sinclair Logic Chip, commonly called "ULA". The block diagram is shown in Figure 3 with all relevant connections including the buffer resistors R. For reasons of simplicity, only the 2 KB version is shown.

              ULA               ROM        Z80        2K RAM
        ________________       _____       _____       _____ 
VIDEO<-| VSHFTREG <-DATA|-----|DATA |-----|DATA |--R--|DATA |  
       | *LINECTR ->A0-2|-----|A0-2 |--R--|A0-2 |-----|A0-2 |
       | CHRLATCH ->A3-8|-----|A3-8 |--R--|A3-8 |-----|A3-8 |
       |                |     |A9-12|-----|A9-13|-----|A9-11|
       |           ROMCS|-----|CE   |     | *INT|-----|A6   |
       |                |     |_____|     |     |     |     |
       |           RAMCS|-----------------|-----|-----|CE/OE|
       |             A14|-----------------|A14  |     |_____|
       |             A15|-----------------|A15  |        
       |              WR|-----------------|WR   | 
       |              RD|-----------------|RD   |
       |              M1|-----------------|M1   |
       |            MREQ|-----------------|MREQ |
       |            IORQ|-----------------|IORQ | 
       |             NMI|-----------------|NMI  |   
       |            HALT|-----------------|HALT |
       |________________|                 |_____| 

Figure 3: Circuit for generating pseudo high-resolution graphics

The only difference between Pseudo Hires and the character representation is the *ULA LCNTR and the use of the INT input. The pseudo-high-resolution graphics generation routines do not use the INT input, and the ULA's LCNTR is reset to zero at each horizontal line. The exception is the software XTRICATOR, which uses INT and uses the I register in two ways: firstly as part of the RST vector in INT mode 2 when the CPU is interrupted at the end of a horizontal line and secondly as a pointer to during a refresh the character generator in ROM.


8. Actual high-resolution graphics hardware

The actual hardware required to produce high-resolution graphics consists of the Z80 CPU, RAM, video shift register, and circuitry within the ULA to generate the SYNC pulses. This is shown in Figure 4 with all relevant connections. Again, for the sake of simplicity, the 2 KB version is shown, but this also applies to larger SRAM versions. If a 16K RAMPACK is used, small hardware modifications must be made to it in order to output video data during the refresh cycle, which is required for this type of high-resolution graphics generation. What these hardware changes consist of in detail will be discussed later.

              ULA               ROM         Z80       2K RAM
        ________________       _____       _____       _____ 
VIDEO<-| VSHFTREG <-DATA|-----|DATA |-----|DATA |--R--|DATA |
       |                |     |A0-12|-----|A0-15|-----|A0-10|
       |           ROMCS|-----|CE   |     |     |     |     |
       |                |     |_____|     |     |     |     |
       |           RAMCE|-----------------|-----|-----|CE/OE|
       |             A14|-----------------|A14  |     |_____|
       |             A15|-----------------|A15  |
       |              WR|-----------------|WR   |
       |              RD|-----------------|RD   |
       |              M1|-----------------|M1   |
       |            MREQ|-----------------|MREQ |
       |            IORQ|-----------------|IORQ |
       |             NMI|-----------------|NMI  |
       |            HALT|-----------------|HALT |
       |________________|                 |_____|

Figure 4: Circuit for generating actual high-resolution graphics

With the exception of the WRX1K software, which produces a small high-resolution graphic on a 1K ZX81, all Hires programs require a 6 KB HFILE graphics memory. Sufficient RAM can be provided by modifying a standard 16K RAMPACK with a few diodes and a resistor.

The RAMPACK must be changed so that video data can be output during the refresh cycle. This is done by breaking the RD and RFSH lines at the connector and just inserting two 1N34A germanium diodes and a 4.7k pullup resistor. All changes are made at your own risk!

                            +5V
                             |
                           [4.7K]
                     1N34A   |
connector   RD _______|/|____|_____________ RD OF RAMPACK
                      |\|    |
                             |
connector RFSH _______|/|____|     +5V_____ RFSH of RAMPACK
                      |\|
                     1N34A

Figure 5: Changes to the RAMPACK for actual high-resolution graphics


9. Timing sequences in the ZX81 character display

All Sinclair ZX81 imaging hardware as shown in Figure 2 is required to display a standard image consisting of 24 lines of 32 characters each. Character display begins after the last blank line at the top of the screen when the video routine jumps to DFILE + 32K. The hardware in the ZX81 is activated when any op-code over 32K is executed (A15 high and M1 low) and data bit 6 is low. The video data is processed in these simplified steps:

  1. The ULA loads the character code into an address register within the ULA
  2. The ULA pushes the data lines low
  3. The CPU interprets the pressed data lines as NOP
  4. The ULA generates part of the address to the character generator and the CPU generates the character generator pointer with the I register
  5. The video data is loaded into the ULA's video shift register

One could say that the DFILE is executed character by character with NOPs: for each character there is a NOP. Each NOP instruction requires four clock cycles of the CPU at 3.25 MHz to execute, which is as long as 8 pixels at 6.5 MHz on the ULA's video shift register.

           <--------CHARACTER 1-----------><--------CHARACTER 2----------->
   T STATE <--T1--><--T2--><--T3--><--T4--><--T1--><--T2--><--T3--><--T4-->
     (ref)  ___    1___2   3___     ___5    ___     ___     ___     ___
 CPU CLOCK |   |___|   |___|   |___|   |___|   |___|   |___|   |___|   |___|
           _ _______________ _______________ _______________ _______________
    A0-A15 _X_____PC________X___I+CHR+ULA___X______PC_______X___I+CHR+ULA___X
                ________ NOP __________ ________ NOP __________
      DATA >---|__CHR___|_____|_ROM DATA_|-----|__CHR___|_____|_ROM DATA_|--

Figure 6: Time sequences in character representation

In detail, the processes for each byte of character data are shown in Figure 7 and explained as follows:

  1. Each character code (CHR$) in DFILE is addressed by the PC (program counter) of the Z80 CPU. On the rising edge T2, the byte is loaded from DFILE into the ULA: bits 0-5 are loaded into a 6-bit address latch, whereas bit 7 sets or resets the inversion flag.
  2. At the falling edge T2, the ULA forces all data lines low.
  3. At the rising edge T3, the data lines pushed to zero are recognized by the CPU as a NOP command.
  4. During T3/T4, the CPU executes the refresh cycle and the ROM addresses are generated using the I register on A9-A15, the 6-bit ULA character register on A3-A8 and the ULA line counter modulo 8 on A0-A2.
  5. When bit 7 in the ULA's character code latch is high, the video data is inverted.
  6. This repeats itself until a HALT command is fetched.
  7. In the op code for the HALT command, data bit 6 is "high", which is why this command is not replaced by a NOP but is actually executed.
  8. Regardless of CPU timing, the ULA generates an HSYNC pulse and increments the line counter LCNTR.
  9. The CPU continues to perform NOPs, incrementing the R register and reading the INT input on each rising edge T4.
  10. If A6, hardwired to the INT input, goes low (bit 6 of the R register = 0) during the refresh cycle, the Z80 executes the interrupt handling routine below 32K.
  11. The CPU returns from the interrupt handler and resumes "execution" of DFILE opcodes.
  12. These processes are repeated 192 times. Then the INT routine returns to the main video routine, turns on the NMI generator and switches back to the user program.

10. Timings in Pseudo Hires

With a few exceptions, all ZX81 character display hardware is used as shown in Figure 2 to produce a standard 192-line image with 32 seemingly high-resolution patterns. Patterns begin displaying after the last blank line at the top of the screen when the video routine jumps to the 6K HFILE + 32K. The hardware in the ZX81 is activated when any op-code over 32K is executed (A15 high and M1 low) and data bit 6 is low. The video data is processed in these five steps:

The video data is loaded in five steps:

  1. The ULA loads the character code into an address register within the ULA
  2. The ULA pushes the data lines low
  3. The CPU interprets the pressed data lines as NOP
  4. The ULA generates a part of the address on the character generator and the CPU generates the high-order address part on the character generator pointer (MSB) with the I register
  5. The sample data of the apparently high-resolution graphics is loaded into the ULA's video shift register

Each NOP instruction requires four clock cycles of the CPU at 3.25 MHz to execute, which is as long as 8 pixels at 6.5 MHz on the ULA's video shift register.

           <--------CHARACTER 1-----------><--------CHARACTER 2----------->
   T STATE <--T1--><--T2--><--T3--><--T4--><--T1--><--T2--><--T3--><--T4-->
     (ref)  ___    1___2   3___     ___5    ___     ___     ___     ___
 CPU CLOCK |   |___|   |___|   |___|   |___|   |___|   |___|   |___|   |___|
           _ _______________ _______________ _______________ _______________
    A0-A15 _X_____PC________X___I+CHR+ULA___X______PC_______X___I+CHR+ULA___X
                ________ NOP __________ ________ NOP __________
      DATA >---|__CHR___|_____|_ROM DATA_|-----|__CHR___|_____|_ROM DATA_|--

Figure 7: Time sequences in the creation of apparently high-resolution graphics

The exact sequence of operations for each individual character byte is as follows:

  1. Each character code (CHR$) in DFILE is addressed by the PC (program counter) of the Z80 CPU. On the rising edge T2, the byte is loaded from DFILE into the ULA: bits 0-5 are loaded into a 6-bit address latch, whereas bit 7 sets or resets the inversion flag.
  2. At the falling edge T2, the ULA forces all data lines low.
  3. At the rising edge T3, the data lines pushed to zero are interpreted by the CPU as a NOP command.
  4. During T3/T4, the CPU executes the refresh cycle and the ROM addresses are generated using the I register on A9-A15, the 6-bit ULA character register on A3-A8 and zero on the address outputs A0-A2.
  5. On the falling edge T4, the video data is loaded into the video shift register and 8 pixels are shifted out at 6.5MHz.
  6. When bit 7 in the ULA's character code latch is high, the video data is inverted.
  7. The CPU increases its PC and gets the next op code.
  8. This repeats until a RET instruction causes the CPU to return to the Hires routine.
  9. In the op code for the RET command, data bit 6 is "high", which is why this command is not replaced by a NOP but is actually executed.
  10. Regardless of the CPU timing, the ULA generates an HSYNC pulse and increases the line counter LCNTR, but the software resets the LCNTR directly to zero.
  11. The CPU returns from the Hires routine and begins "executing" the character codes from DFILE again.
  12. These processes are repeated 192 times. The hires routine then ends with switching on the NMI generator and switching back to the user program.

11. Time sequences with real high-resolution graphics

Although I will use the WRX hires core routines as an example, the software for producing true high-resolution graphics, developed independently by others, is very similar to this.

The display of high-resolution graphics begins after the last blank line at the top of the screen when the NMI handler jumps into the HR video routine using the IX register pair. HR sets the address pointers in the I and R registers to the high-resolution graphics file (HFILE), then the HR routine jumps to the mirrored LBUF routine over 32K and loads the R register, finally it branches after the first NOP opcode.

The ULA loads the video shift register when any op-code is executed with A15 high, M1 low and data bit 6 equal to zero.

The hiring data is processed in three steps:

  1. The CPU executes each of the 4 clock cycle NOP instructions.
  2. During clock cycles T3/T4 (refresh), the contents of the I and R registers appear on address lines A0-15.
  3. The byte containing the Hires data addressed by I and R is loaded into the ULA shift register.

LBUF consists of 32 NOPs, each requiring four clock cycles to execute. During the second part of the execution of the NOP opcode, the I and R registers address the Hires data in HFILE, which the ULA loads into the video shift register.

The ROM address for the character generator generated by the ULA is not used because ROMCS is not activated because the I register (A8-15) points to a point in the RAM-based HFILE.

           <---------HIRESBYTE1-----------><---------HIRESBYTE2----------->
   T STATE <--T1--><--T2--><--T3--><--T4--><--T1--><--T2--><--T3--><--T4-->
            ___     ___     ___     ___     ___     ___     ___     ___
 CPU CLOCK |   |___|   |___|   |___|   |___|   |___|   |___|   |___|   |___|
           _ _______________ _______________ _______________ _______________
    A0-A15 _X______PC_______X______I_+_R____X______PC_______X______I_+_R____X
                ____________    _________       ____________    _________
      DATA >---|_____NOP____|--|__HIRES__|-----|_____NOP____|--|__HIRES__|--

Figure 8: The timing of creating actual high-resolution graphics

The details of processing each Hires byte are as follows:

  1. The first opcode of the LBUF routine, LD R,A, is executed.
  2. The following 32 NOPs in LBUF are executed one after the other.
  3. On the rising edge of T3 each time the op code is loaded, the CPU executes a NOP.
  4. During T3/T4, the address is generated from the R register on A0-7 and the I register on A8-15.
  5. On the falling edge of T4, the Hires data from HFILE is loaded into the ULA's video shift register and shifted out 8 pixels at 6.5MHz.
  6. The CPU increments the PC and the R register and gets the next NOP and thus the next hires byte.
  7. This process is repeated 32 times.
  8. The last op-code of LBUF, JP (IX), is executed to return to the HR routine.
  9. The ULA generates an HSYNC pulse.
  10. The HLINE routine increments the I/R register pair by 32 and jumps back to the 32 NOP containing LBUF routine above 32K.
  11. This process is repeated 192 times.
  12. The Hires Video routine can call the Sinclair character display routine to display the bottom lines and restores the register contents etc. to return to the user program.

Like the other Hires routines, WRX excludes the Sinclair video logic by loading a new video routine vector into the IX register.


12. ZX81 SLOW MODE VIDEO ROUTINES

As shown in Table 1, the CPU multitasks between the video routines and the user program in four time periods.

1. VSYNC-Interval

0229 DISPLAY-1 Decrements the line counter
023E DISPLAY-2 Queries keyboard (at the beginning of VSYNC)
0277 OUT FF,A Ends the VSYNC pulse
0292 DISPLAY-3 Saves the video vector in IX (0281) and returns to the user program

2. User program

0066 NMI Counts empty lines, returns to the user program or branches to 0281 using JP (IX).

3. Representation of the DFILE

0281 VIDEO-1 Sets display parameters, CALL 2B5 (RETURN VIA INT)
02B5 DISPLAY-5 Sets display parameters, enables interrupt, JP (DFILE)
XXXX (DFILE) Executes HALTs and forced NOPs within the DFILE
0038 INT Decrements the row/line counter and returns to DFILE or 028B
0292 DISPLAY-3 Saves the video vector (028F)

4. User program

0066 NMI Counts empty lines, returns to the user program or branches to 028F using JP (IX).
028F VIDEO-2 JP 229 back to the frame counter in block 1

The ZX81 video routines follow with fully documented listings that contain more detail than the listing from the IAN LOGAN ZX81 DISASSEMBLY. Needless to say, I used Dr. Ian Logan's book intensively during my research into the ZX81 video code.

Note: Only the code relevant to image generation is shown here.

0038               ; INT SERVICE ROUTINE
     DEC C         ; decrement the scan line counter in register C
     JP NZ 0045    ; go SCAN-LINE : repeats 8 times for each DFILE character row
     POP HL        ; point to the start of next DFILE row
     DEC B         ; decrement the ROW counter in register B
     RET Z         ; return to 028B
     SET 3,C       ; load scan line counter in register C with 08 scan lines

0041               ; WAIT-INT
     LD R,A        ; load value DD into register R
     EI            ; enable INT
     JP (HL)       ; execute the NOPs in DFILE

0045               ; SCAN-LINE
     POP DE        ; discard the return address
     RET Z         ; delay (never returns)
     JR 0041       ; got WAIT-INT
                   ; ----------------------------------------------------------

0066               ; NMI SERVICE ROUTINE
                   ; Interupts application program every 64 usec (HSYNC)
     EX AF,AF'     ; retrieve blank line counter in AF'
     INC A         ; next blank line
     JP M 006D     ; RETURN via 006D if AF' = FF (to NMI-EXIT)
     JR Z 006F     ; JR NMI-CONT if last line
006D EX AF,AF'     ; save blank line counter
     RET           ; return to application or NMI-EXIT

006F               ; NMI-CONT
     EX AF,AF'     ; retrieve main register AF
     PUSH AF       ; now save the application program registers
     PUSH BC
     PUSH DE
     PUSH HL
     LD HL,(DFILE) ; needed only if IX=0281 and
     SET 7,H       ; if DFILE is executed
     HALT          ; 1T state synchronization: this HALT is used with special
                   ; hardware connected to the CPU WAIT and HALT lines and is
                   ; released and synchronized on the falling of the NMI pulse

007A               ; NMI-EXIT
     OUT FD,A      ; turn off NMI generator
     JP (IX)       ; to VIDEO-1 or VIDEO-2
                   ; ---------------------------------------------------------

0229               ; DISPLAY-1
     LD HL,(FRAMES); get the system variable FRAMES
     DEC HL        ; decrement each frame
     .....
     LD (FRAMES),HL; save the system variable FRAMES

023E               ; DISPLAY-2
     CALL 02BB     ; read the keyboard and load MARGIN with blank lines
     .....         ; also starts the VSYNC pulse
0277 OUT FF,A      ; stops the VSYNC pulse
     LD HL,(DFILE) ; (FAST VIDEO only) - point HL to first HALT for blank lines
     SET 7,H       ; (FAST VIDEO only) - DFILE echo above 32K
027E CALL 0292

0281               ; VIDEO-1
                   ; this vector is saved in register IX at 0292
     LD A,R        ; delay
     LD BC,1901    ; set up INT parameters for first HALT at (DFILE)
     LD A,F5       ; set up R register for first HALT at (DFILE)
     CALL 2B5      ; continue setup for DFILE display and return via INT
028B               ; return here from last INT
     DEC HL        ; (FAST VIDEO only) - point HL to last HALT for blank lines
     CALL 292      ; save VIDEO vector in IX, calculate blank lines, POP regs

028F JP 0229       ; VIDEO-2
                   ; this vector is saved in register IX at 0292

0292               ; DISPLAY-3
     POP IX        ; IX=0281 or 028F to vestor to VIDEO-1 or VIDEO-2
     LD C,(IY+56)  ; load number of blank lines from MARGIN (1F in 60 Hz option)
     BIT 7,(IY+59) ; test FAST/SLOW bit
     JR Z,2A9      ; (FAST VIDEO)  branches to generate blank lines
     LD A,C        ; C=(MARGIN)=1F for 60 Hz
     NEG           ;
     INC A         ;
     EX AF,AF'     ; during NMI @ 0066 - AF' is incremented and tested for zero
     OUT (FE),A    ; turn on NMI generator
     POP HL        ; self explanatory
     POP DE
     POP BC
     POP OFF
     RET           ; return to application program interupted every HSYNC by NMI
                   ; -----------------------------------------------------------

02B5               ; DISPLAY-5
     LD R,A        ; R increments with each opcode on A0 to A7 during RFSH
                   ; until A6 goes low which generates the INT signal.
     LD A,DD       ; Set the left margin of all other lines, load to R at 0041
     EI            ; Now that R is set up enable INT
     JP (HL)       ; "executes" the DFILE starting with HALT and waits for the
                   ; first INT to come to the rescue.
                   ; -----------------------------------------------------------

13. ZX81 FAST MODE VIDEO ROUTINES

To reduce the time required to execute the user program, FAST MODE uses 100% of the CPU time. However, there are times when the user program is not active. When the program is STOPped or PAUSED or waiting for INPUT from the keyboard, the keyboard is polled and if no key is pressed, the image is generated independently of NMI pulses.

In fact, the ZX81 FAST MODE video routines have been implemented to be compatible with the ZX80 hardware, so that the ZX81 ROM can be used as a "refresher" in the ZX80.

Since the ZX80 creates the blank lines using software, the ZX81 ROM does the same when it is in FAST MODE.

The loop of video routines for FAST MODE start with the FRAME/KBD/VSYNC routine at 229h:

   0229 DECREMENT FRAME COUNTER
   023D EXIT FAST VIDEO IF FRAMES=0 (END OF PAUSE)
   023E CHECK KEYBOARD
   0260 EXIT FAST VIDEO IF NEW KEY PRESSED
   0292 SAVE THE VIDEO POINTER IN IX (0281)
   029B JR Z 02A9 TO BLANK LINE ROUTINE
   02A9 GENERATE BLANK LINES
   02B3 JP (IX) TO 0281
   0281 GENERATE THE DFILE DISPLAY
   0292 SAVE VIDEO POINTER (028F)
   029B JR Z 02A9 TO BLANK LINE ROUTINE
   02A9 GENERATE BLANK LINES
   02B3 JP (IX) TO 028F)
   028F JP 229 BACK TO FRAME COUNTER

Since the majority of the routines have already been covered in the SLOW MODE VIDEO chapter, only the differences are described here.

Since most of the routines were described in the SLOW MODE VIDEO chapter, only the differences are described here. Compare the way the SLOW mode enters this loop from end of blank line application program execution by saving the main registers of the program and restoring them at the end of 0292. By contrast, the FAST mode does not save any registers and branches out of the 0292 restore main register routine to literally generate the blank lines. This is done at 029B after testing the FAST flag and jumping to a less known routine called DISPLAY-4

02A9 ; DISPLAY-4
   LD A,FC        ; first R delay to INT
   LD B,01        ; one row
   CALL 02B5      ; display blank lines
   DEC HL         ; point back to HALT
   EX (SP),HL     ; delay 19T
   EX (SP),HL     ; delay 19T
   JP (IX)        ; IX = 0281 or 028F

The routine at 02A9 is called twice each frame to generate the top and bottom blank lines with HL pointing to either the first HALT at the start of DFILE or the last HALT at end of DFILE. Reg C holds the number of blank lines and reg B is set up for 1 row. After VSYNC the 31 top blank lines are generated by calling the diplay routine at 02B5 and excecuting the first HALT at the START of DFILE 31 times. After returning from the display routine HL points to the last HALT+1 and DEC HL is required point HL back to the last HALT of DFILE. After saving the return address in IX, the routine at 029A is reentered with HL pointing to the last HALT and generates the bottom 31 blank lines by excecuting the HALT at the END of DFILE.


14. TRUE HIRES VIDEO SOFTWARE

The true hires core routines are distinguished by the use of the I and R register pair as address pointers for the display file. The only other requirement is to execute 32 NOP instructions (or equal) per horizontal line and to update the I and R registers during HSYNC time. More blank lines can be used above and below the display for faster application execution. The listings are compatable source code for the ZXAS assembler both on the ZX81 and under XTender, the ZX81 emulator form CARLO DELHEZ.

Check current version of XTender for hires compatability.

These ASCII listings can be used to prepare a formatted 2 REM .l file with the ZXAS.COM program from Jack Raats.


WRX16 - 1984 Wilf Rigter

This is the hires core used in programs by FRED NACHBAUR and GREG HARDER. It creates a 256×192 high resolution display in a 6144 byte array starting at (HFILE), which can be poked directly from BASIC programs. START is used to start the hires display and STOP restores the SINCLAIR video. It has a characteristic signature with the I register value greater than 2000 hex. PART 1 calls LBUF 192 times, displaying 256×192 pixels, calculates blank lines, saves pointer to PART 2 in IX and returns to application code. PART 2 calls VSYNC etc, calculates blank lines, saves pointer to PART 2 in IX and returns to application code execution.

      ; ORIGIN = 16516 (hex 4084)

LBUF  ; Displays one line of 256 pixels
      ; ------------------------------

      ; like DFILE, it is called above 32K to activate the ULA video
      ; hardware. The hires bytes may be inverted for special effects
      ; by setting bit 7 of the NOP codes . The hires data is loaded
      ; into the ULA video shift register during the refresh cycles of
      ; the 32 NOP opcodes when the I and R registers sequentially
      ; address 32 bytes of hires data in the 6144 byte HFILE

LBUF
      LD R,A     ; Now load R register
      00 00 00 00; 32 bytes of 8 pixels
      00 00 00 00
      00 00 00 00
      00 00 00 00
      00 00 00 00
      00 00 00 00
      00 00 00 00
      00 00 00 00
      JP (IX)    ; Return to HR


HR    ; HIRES DISPLAY ROUTINE PART 1
      ; ----------------------------
      LD B,04    ; load delay
HR0   DJNZ HR0   ; delay 56T states to synchronize with HSYNC pulses.
      LD HL,(HFILE); RAMTOP points to the first HFILE byte
      LD B,C0    ; 48 horizontal lines
      LD IX,HR1  ; save the return vector in IX (for JP (IX) at end of LBUF)
      JR HR2     ; skip HR1 first time through the loop
HR1   LD DE,20   ; this value for 32 bytes or 256 pixels per line is
      ADD HL,DE  ; added to HL to point to the start of the next HLINE
      DEC B      ; repeat 48 times
      JP Z HR3   ; if this is the last line JP to HR3
HR2   LD A,H     ; the address in HL is then transferred to
      LD I,A     ; I register
      LD A,L     ; and during LBUF to the R register
      JP C084    ; jump to LBUF @ 4084 + 8000 to start the ULA
HR3   LD IX,HR4  ; save the video vector so that NMI returns to HR4
      JR HR5     ; now get blank lines and return to application code

HR4   ; HIRES DISPLAY ROUTINE PART 2
      ; ----------------------------
      CALL 220   ; first PUSH registers then jump to VSYNC, etc
      LD IX,WRX16; save the video vector so that NMI returns to HR
HR5   LD A,(4028); 33 or 19 blank lines in system variable MARGIN
      JP 29E     ; save blank lines, start NMI, POP registers and RETURN

      ; --------------------- end of listing ---------------------

The hires video is started and stopped by changing the vector address
in register IX which is used by NMI to JP (IX) to the video routine.
The following routines are synchronized with the display so that the
changeover in video mode occurs without display breakup.

STOP  ; STOP hires video and return to SINCLAIR video
      ; ---------------------------------------------
      LD HL,0281 ; pointer to SINCLAIR video routine
      LD A,1E    ; SINCLAIR ROM pattern table MSB base address (1E00)
      LD I,A     ; pointer to I register
      JR SYNC

START ; Start the hires video
      ; ---------------------
      LD HL,HR   ; pointer to the hires video routine

SYNC  ; used by START and STOP to smoothly change video mode
      ; ----------------------------------------------------
      PUSH HL
      LD HL,4034 ; FRAMES counter
      LD A,HL    ; get old FRAMES
SYNC1 CP A,(HL)  ; compare to new FRAMES
      JR Z SYNC1 ; exit after a change is detected
      POP IX     ; SINCLAIR video routine


     ; -------------- END OF LISTING ---------------

GUUS-FLATER by ENNO BORGESTEEDE (1984)

This hires core uses a ingenious way to intercept the video vector. Instead of changing the value of the IX register, GUUS-FLATER intercepts at the beginning of the DFILE execution by changing the first 4 bytes including the HALT to DI and JP 409F which is the start of the hires routine. At the end of the hires screen the program simply returns to ROM routine at xxxx. It has a characteristic DFILE starting with the DI and JP 409F and the HFILE starts at (4004)

(400C)  DI           ; these bytes are loaded into DFILE
        JP 409F      ; to vector to the hires routine

409F    LD B,08      ; delay
40A1    DJNZ 40A1    ; delay
        LD A,R       ; delay
        LD B,C0      ; 192 lines
        LD DE,20     ; 32 bytes per line
        LD HL,(4004) ; hires file (HFILE) starts at RAMTOP
40AD    LD A,H       ; MSB address of HFILE
        LD I,A       ; load MSB of HFILE pointer
        LD A,L       ; LSB address of HFILE
40B3    JP C0B6      ; JUMP above 32K
40B6    LD R,A       ; load LSB of HFILE pointer
40B8    "COPYRIGHT 1984 ENNO BORGESTEEDE " ; same as 32 NOPs
40D8    JP 40DB      ; JUMP below 32K
40DB    ADD HL,DE    ; next hires line
40DC    DJNZ 40AD    ; next line repeats 192 times
40DE    LD A,1E      ; restore ROM pattern table pointer
40E0    LD I,A       ; load pointer
40E2    RET          ; join the SINCLAIR video in progress

HRG7 - in progress


WRX16K - 1996 Wilf Rigter rigter@cafe.net

The original WRX was written in 1984 but recent renewed interest has yielded newer more efficient versions. WRX16K 1996 is the most compact version of the WRX yet and will display a true bit mapped 256×192 hires screen. It was designed to work with the modified 16K RAMPACK or 16K SRAM and you must first lower RAMTOP with POKE 16389,96 then NEW before loading.

The hires mode can be started and stopped with the same routines shown in the WRX16 listing above. The simple START is used for starting the hires mode by changing video vector address in the IX register. Hires is stopped with the inline code segment called "BREAK" which returns synchronously to the Sinclair video mode when the space key is pressed. The HFILE is a 6K linear array starting at (4004) but is easily relocated.

Note that HFILE must start on a 32 byte boundary (2000,2020, etc).

ORG   16516       ; (hex 4084)

START LD IX,HR    ; simple start of the hres mode
      RET

LBUF LD R,A       ; load HFILE address LSB
      0 0 0 0     ; 32 NOPs = 256 pixels
      0 0 0 0
      0 0 0 0
      0 0 0 0
      0 0 0 0
      0 0 0 0
      0 0 0 0
      0 0 0 0
      RET NZ      ; always returns

HR
      LD B,7      ; delay
HR0   DJNZ HR0    ; delay
      DEC B       ; reset Z flag
      LD HL,(4004); HFILE starts at RAMTOP or HSCRN (note below)
      LD DE,20    ; 32 bytes per line
      LD B,C0     ; 192 lines per hires screen
HR1   LD A,H      ; get HFILE address MSB
      LD I,A      ; load MSB into I register
      LD A,L      ; get HFILE address LSB
      CALL C089   ; CALL LBUF + 8000
      ADD HL,DE   ; next line
      DEC B       ; dec line counter
      JP NZ HR1   ; last line
HR2
      CALL 292    ; return to application program
      CALL 220    ; extra register PUSH and VSYNC
BREAK             ; this code segment is optional
      CALL F46    ; check break key
      LD A,1E     ; restore pattern table pointer
      LD I,A
      JR NC STOP  ; skip the HR vector load if BREAK
      LD IX,HR    ; load the HR vector
STOP  JP 2A4      ; return to application program

HSCRN 2000        ; this is used with SRAM at 8K - 16K

; ------------------ end of listing ------------------

Note: HFILE can be relocated to use SRAM between 8 to 16K by changing LD HL,(4004) to LD HL,(HSCRN).


WRX1K -1996 Wilf Rigter

This little true hires program is special because it runs on a 1K/2K ZX81. It creates a miniature 64x48 high resolution display in a 384 byte array starting at (RAMTOP), which can be poked directly from BASIC programs. When START is called, it collapses the SINCLAIR DFILE and expands the hires file above RAMTOP in order to efficiently utilize the 1K memory. When STOP is called, it recovers the space above RAMTOP to make more RAM available for DFILE.

      ; ORIGIN = 16516 (hex 4084)

LBUF  ; Dummy display file
      ; ------------------

      ; like DFILE, it is called above 32K to activate the ULA video
      ; hardware but only bit 7 character code data is used. The hires
      ; data is loaded into the ULA video shift register during the refresh
      ; cycles of the 8 NOP opcodes when the I and R registers
      ; sequentially address 8 bytes of hires data in the 384 byte HFILE
      ; NOTE: in this special case of short (8 byte) video lines the delay
      ; opcodes (E3 and 40) have bit 6 high to suppress the video display
      ; at the start and end of each horizontal line

      E3 E3 E3 E3; Delay 76T states
      LD R,A     ; Now load R register
LBYTE 00 00 00 00; 8 bytes of 8 pixels
      00 00 00 00;
      40 40 40 40; Delay 20T states
      40         ; Delay 4T
      JP (IX)    ; Return to HR

START ; Makes room above RAMTOP and starts the hires routine
      ; ----------------------------------------------------
      LD IX,HR    ; This is the start of the new video routine
      LD BC,180   ; 384 bytes are required for a 64X48 display
      CALL EC5    ; is there enough room to lower ramtop?
      LD HL,(4004); get the old RAMTOP (1K/2K)
      CCF         ; calculate the new RAMTOP value by
      SBC HL,BC   ; Subtracting the HFILE length

STACK ; used by START and STOP to change RAMTOP without NEW

      OUT FD,A    ; Turn off the NMI generator during STACK move
      LD (4004),HL; Save RAMTOP value in the sytem variable
      DEC HL      ; point to the first byte below RAMTOP
      LD (HL),3E  ; and mark it with 3E
      DEC HL
      LD SP,HL    ; Load the STACK POINTER
      DEC HL
      DEC HL
      LD (4002),HL; Load the ERROR STACK POINTER
      OUT FE,A    ; Turn on NMI
      JP 676      ; resume BASIC program execution at NEXT LINE


HR    ; PART 1 calls LBUF 48 times, displaying 64x48 pixels, calculates blank
      ; lines, saves pointer to PART 2 in IX,and returns to application code
      ; PART 2 calls VSYNC etc, checks the BREAK key, calculates blank lines,
      ; saves pointer to PART 1 in IX and returns to application code.

      LD B,04    ; load delay
HR0   DJNZ HR0   ; delay 56T states to synchronize with HSYNC pulses.
      LD HL,(4004); RAMTOP points to the first HFILE byte
      LD B,30    ; 48 horizontal lines
      LD IX,HR1  ; save the return vector in IX (for JP (IX) at end of LBUF)
      JR HR2     ; skip HR1 first time through the loop
HR1   LD DE,08   ; this value for 8 bytes or 64 pixels per line is
      ADD HL,DE  ; added to HL to point to the start of the next HLINE
      DEC B      ; repeat 48 times
      JP Z HR3   ; if this is the last line JP to HR3
HR2   LD A,H     ; the address in HL is then transferred to
      LD I,A     ; I register
      LD A,L     ; and during LBUF to the R register
      JP C084    ; jump to LBUF (4084 + 8000) to start the video
HR3   LD IX,HR4  ; save the video vector so that NMI returns to PART 2
      JR HR5     ; now get blank lines and return to application code

HR4   ; PART 2

      CALL 220   ; first PUSH registers then jump to VSYNC, etc
      CALL F46   ; test the BREAK key
      JR NC STOP ; and exit HR if key is down
      LD IX,HR   ; save the video vector so that NMI returns to PART 1
HR5   LD A,(4028); 33 or 19 blank lines in system variable MARGIN
      ADD A,47   ; 71 more blank lines for fast application code execution
      JP 29E     ; save blank lines, start NMI, POP registers and RETURN
STOP             ; EXIT hires video to restore RAMTOP and SINCLAIR video
      LD A,1E    ; SINCLAIR ROM pattern table MSB base address (1E00)
      LD I,A     ; pointer to I register
      LD HL,(4004); load the current RAMTOP
      LD DE,180  ; HFILE length
      ADD HL,DE  ; is added to the current RAMTOP
      LD IX,0281 ; SINCLAIR video routine
      JR STACK   ; exit HR via STACK to change RAMTOP


     ; ------------------END OF LISTING----------------

15. PSEUDO HIRES CORE ROUTINES

Pseudo hires software uses CHR$ code + register I to address 1 of 64 pattern bytes in the ROM. The CHR$ codes are limited 0 to 63 and their inverse (128 to 191) and the value of I is choses to point to a block of pattern bytes in ROM which has the greatest randomness and least duplication of values. This method is called "pseudo" hires because fewer than 50% of the 256 patterns required for true hires are available for display. This results in incomplete or missing pixel patterns but for many application (games etc) this is not a problem. The advantage of this method is the fact that it runs on systems the standard 16K RAMPACK and is emulated by Xtender.
It has a characteristic 6336 byte DFILE consisting of 192 RET opcodes spaced at 33 byte intervals.


ROCK CRUSH by Steve McDonald

In progress


3DHIRES author unknown

In this example core routine the expanded hires "DFILE" starts at 6700 hex

START   LD A,04   ; select an "interesting" pattern table
        LD I,A    ; load into ROM pattern table pointer
        LD IX,42C4; load pseudo hires vector
        RET

42C4    LD HL,E6DF; HL is used as the hires "DFILE" pointer
        LD DE,0021; 32 CHR$ + RET = 33 bytes per line
        DI        ; INT not used
        LD C,FE   ; IO address to reset the ULA row counter
        LD B,16   ; delay
42CF    DJNZ 42CF ; delay
        LD B,C0   ; 192 lines of 33 bytes
42D3    IN A,(C)  ; apply reset to row counter
        OUT FF,A  ; release reset from row counter
        ADD HL,DE ; next "DFILE" line (E700,E721, etc)
        CALL 42EC ; "execute" the "DFILE" via JP (HL) at 42EC
        DEC B     ; decrement line counter
        JP NZ 42D3; last line? repeat 192 times.
        CALL 0292 ; restore main registers, return to application
        CALL 0220 ; extra register PUSH then VSYNC
        LD IX,42C4; hires routine vector
        JP 02A4   ; restore main registers, return to application
        JP (HL)   ; jump to the hires "DFILE" echo above 32K

XTRICATOR by Software Farm

This unusual pseudo hires core routine intercepts the video by setting INT mode 2 in which the interrupting device supplies part of the INT vector address. Since the idle data bus is FF and the I register is set to 40, the INT vector is 4000 when the A6 line interupts at the end of the horizontal line.

4083    LD HL,40A5 ; return vector
4086    PUSH HL    ; save vector
4087    LD HL,E500 ; hires file
408A    PUSH HL    ; save vector
408B    LD B,07    ; delay
408D    DJNZ 408D  ; delay
408F    LD A,1E    ; Sinclair ROM patterns
4091    LD I,A     ; load pattern table pointer
4093    LD DE,C201 ; D = 194 lines
4096    DEC E      ; set Z FLAG
4097    JP Z 409A  ; delay
409A    POP HL     ; HL = E500
409B    DEC D      ; decrement the line counter
409C    RET Z      ; after last line only : return to 40A5
409D    SET 0,E    ; E = 01
409F    JP 40A2    ; delay
40A2    LD A,00    ; delay
40A4    JP (HL)    ; jump to 6500 above 32K

40A5    LD A,04    ; "special" ROM pattern
40A7    LD I,A     ; load pattern table pointer
40A9    RET        ; return to xxxx

40AA    LD A,40    ; INT mode 2 MSB address
40AC    LD I,A     ; load the INT mode 2 vector
40AE    IM2        ; start INT mode 2
40B0    RET

40B1    LD A,1E    ; Sinclair ROM patterns
40B3    LD I,A     ; load pattern table pointer
40B5    IM1        ; restore INT mode 1
40B7    RET

40B8    LD HL,6500 ; load a call instruction
40BB    LD B,C1    ; the start of HFILE (E500)
408D    LD (HL),CD ; CALL 4096
408F    INC HL
40C0    LD (HL),96
40C2    INC HL
40C3    LD (HL),40
40C5    INC HL
40C6    LD A,20     ; generate a 6144 spaces starting 
40C8    LD (HL),00  ; at 6503
40CA    INC HL
40CB    DEC A
40CC    JR NZ 40C8
40CE    DJNZ 40C6 
40D0    RET